from collections import namedtuple
import uuid

from sqlalchemy.sql import select, and_
#from sqlalchemy import func

from cpswarehouse.models import (racial_fact,
                                 school_dimension,
                                 year_dimension,
                                 grade_dimension,
                                 DBSession, meta,
                                 YearDimension)

session = DBSession()

class StarQueryReuseException(Exception): pass

class StarQueryGroupByException(Exception): pass


#this mapping might be uncessecary as this data should be
#in the column already, but it also serves as a catalog of
#allowed (known) columns that we can query from
COLUMNS_TO_NAMES = { 
    year_dimension.c.year:'year',
    grade_dimension.c.grade:'grade',
    school_dimension.c.name:'school',
    racial_fact.c.asian: 'asian',
    racial_fact.c.black: 'black',
    racial_fact.c.hispanic: 'hispanic',
    racial_fact.c.nativeamerican: 'nativeamerican',
    racial_fact.c.white:'white',
    }

class StarQuery(object):
    
    def __init__(self, columns_to_names, fact_table):
        self.fact_table = fact_table
        self.selects = set()
        self.joins = set()
        self.group_by = None
        self.where = None
        self.query = None
    
        self.extended_aliases = dict(columns_to_names)

    
    def _join(self, join_column):
        self.joins.add(join_column)


    def _group_by(self, group_by_column):
        if(self.group_by is not None):
            raise StartQueryGroupByException("Only one column may be specified for grouping")
        self.group_by = group_by_column

        return self


    def _select(self, select_table, select_column, func=None, join=True):
        if(func is None):
            self.selects.add(select_column.label(self.extended_aliases[select_column]))
        else:
            funked = func(select_column)
            self.selects.add(funked.label(self.extended_aliases[select_column]))

        if join:
            self._join(select_table)


    def _where(self, where_condition):
        if(self.where is not None):
            self.where = and_(self.where, where_condition)
        else:
            self.where = where_condition


    def assemble(self):
        
        selects = list(self.selects)
        joins = list(self.joins)

        if self.group_by is not None:
            selects.append(self.group_by)

        #import pdb; pdb.set_trace()
        
        #start with racial_fact (selecting from there) and
        #join the other required tables
        current_join = self.fact_table
        for join in joins:
            current_join = current_join.join(join)

        #create the query with a where and without
        if self.where is not None:
            query = select(selects, 
                           self.where, 
                           from_obj=[current_join])
        else:
            query = select(selects,
                           from_obj=[current_join])
   

        #append the group_by if it exists
        if self.group_by is not None:
            query = query.group_by(self.group_by)
        
        self.query = query #query is built and ready to be executed


    def fetch_all(self):
        if self.query is None:
            self.assemble()
        else:
            raise StarQueryReuseException("Starquery objects may only be fetched once.")

        #TODO if we really want to abstract this out properly, the binding should
        #probably be more configurable than this (maybe someone isn't using this
        #in a pylons app
        engine = meta.bind.engine
        conn = engine.connect()
        results = conn.execute(self.query)

        for result in results:
            yield RacialFactResultWrapper(result)


class RacialFactResultWrapper(object):
    
    def __init__(self, result):
        self.result = result


    @property
    def nativeamerican(self):
        return self.result.nativeamerican

    
    @property
    def hispanic(self):
        return self.result.hispanic

    
    @property
    def white(self):
        return self.result.white

    
    @property
    def asian(self):
        return self.result.asian

    
    @property
    def black(self):
        return self.result.black
    

    @property
    def year(self):
        return session.query(YearDimension).filter_by(year=self.result.year_id).one()
    



class CpsStarQuery(StarQuery):

    def __init__(self, func=None):
        StarQuery.__init__(self, COLUMNS_TO_NAMES, racial_fact)

        race_columns = [racial_fact.c.asian,
                        racial_fact.c.black,
                        racial_fact.c.hispanic,
                        racial_fact.c.nativeamerican,
                        racial_fact.c.white,]

        for column in race_columns:
            self._select(racial_fact, column, func, join=False) #hackity hack


    def select_year(self, func=None):
        self._select(year_dimension, year_dimension.c.year, func)
        return self


    def select_school(self, func=None):
        self._select(school_dimension, school_dimension.c.school, func)
        return self


    def select_grade(self, func=None):
        self._select(grade_dimension, grade_dimension.c.grade, func)
        return self


    #TODO take id or SchoolDimension object
    def where_school_is(self, school_id): 
        self._where(school_dimension.c.id == school_id)
                
        return self


    #TODO take id or YearDimension object
    def where_year_is(self, year_id, group_by_year=False): 
        self._where(year_dimension.c.year == year_id)
        return self


    def group_by_year(self):
        self._group_by(racial_fact.c.year_id)
        return self


    def group_by_school(self):
        self._group_by(racial_fact.c.school_id)
        return self


    def group_by_grade(self):
        self._group_by(racial_fact.c.grade_id)
        return self


